一个操作系统最核心的内容就是多任务管理,所以我们非常有必要去学习一下 FreeRTOS 的任务创建、删除、挂起、恢复和系统启动等,这样才能对 FreeRTOS 有一个更深入的了解。

  • 调度器开启过程分析
  • 任务创建过程分析
  • 任务删除过程分析
  • 任务挂起过程分析
  • 任务恢复过程分析

调度器开启过程分析

涉及到 ARM 的汇编指令,有关涉及到的 ARM 指令的详细使用情况请参考《权
威指南》的“第 5 章 指令集”。《权威指南》的这一章节对 Cortex-M3/M4 内核的所有指令做了
非常详细的接收,包括指令的含义、使用方法和参考案例。

任务调度器开启函数分析

函数 vTaskStartScheduler()的功能就是开启任务调度器的,这个函数在文件 tasks.c中有定义

(1)、创建空闲任务,如果使用静态内存的话使用函数 xTaskCreateStatic()来创建空闲任务,优先级为 tskIDLE_PRIORITY,宏 tskIDLE_PRIORITY 为 0,也就是说空闲任务的优先级为最
低。

(2)、如果使用软件定时器的话还需要通过函数 xTimerCreateTimerTask()来创建定时器服务任务。定时器服务任务的具体创建过程是在函数 xTimerCreateTimerTask()中完成的。

(3)、关闭中断,在 SVC 中断服务函数 vPortSVCHandler()中会打开中断。

(4)、变量 xSchedulerRunning 设置为 pdTRUE,表示调度器开始运行。

(5)、当宏 configGENERATE_RUN_TIME_STATS 为 1 的时候说明使能时间统计功能,此时需要用户实现宏 portCONFIGURE_TIMER_FOR_RUN_TIME_STATS,此宏用来配置一个定时器/计数器。

(6)、调用函数 xPortStartScheduler()来初始化跟调度器启动有关的硬件,比如滴答定时器、
FPU 单元和 PendSV 中断等等。

内核相关硬件初始化函数分析

FreeRTOS 系统时钟是由滴答定时器来提供的,而且任务切换也会用到 PendSV 中断,这些
硬件的初始化由函数 xPortStartScheduler()来完成,

启动第一个任务

经过上面的操作以后我们就可以启动第一个任务了,函数 prvStartFirstTask()用于启动第一个任务,这是一个汇编函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__asm void prvStartFirstTask( void )
{
PRESERVE8
ldr r0, =0xE000ED08 ;R0=0XE000ED08 (1)
ldr r0, [r0] ;取 R0 所保存的地址处的值赋给 R0 (2)
ldr r0, [r0] ;获取 MSP 初始值 (3)
msr msp, r0 ;复位 MSP (4)
cpsie I ;使能中断(清除 PRIMASK) (5)
cpsie f ;使能中断(清除 FAULTMASK) (6)
dsb ;数据同步屏障 (7)
isb ;指令同步屏障 (8)
svc 0 ;触发 SVC 中断(异常) (9)
nop
nop
}

(1)、将 0XE000ED08 保存在寄存器 R0 中。一般来说向量表应该是从起始地址(0X00000000)开始存储的,不过,有些应用可能需要在运行时修改或重定义向量表, Cortex-M 处理器为此提供了一个叫做向量表重定位的特性。向量表重定位特性提供了一个名为向量表偏移寄存器(VTOR)的可编程寄存器。 VTOR 寄存器的地址就是 0XE000ED08,通过这个寄存器可以重新定义向量表,比如在 STM32F103 的 ST 官方库中会通过函数 SystemInit()来设置 VTOR 寄存器,代码如下:

1
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; //VTOR=0x08000000+0X00

通过上面一行代码就将向量表开始地址重新定义到了 0X08000000,向量表的起始地址存储的就是 MSP 初始值。关于向量表和向量表重定位的详细内容请参阅《权威指南》的“第7章异常和中断”的 7.5 小节。

(2)、读取 R0 中存储的地址处的数据并将其保存在 R0 寄存器,也就是读取寄存器 VTOR中的值,并将其保存在 R0 寄存器中。这一行代码执行完就以后 R0 的值应该为 0X08000000。

(3)、读取 R0 中存储的地址处的数据并将其保存在 R0 寄存器,也就是读取地址 0X08000000处存储的数据,并将其保存在 R0 寄存器中。我们知道向量表的起始地址保存的就是主栈指针MSP 的初始值,这一行代码执行完以后寄存器 R0 就存储 MSP 的初始值。现在来看(1)、 (2)、(3)这三步起始就是为了获取 MSP 的初始值而已!

(4)、复位 MSP, R0 中保存了 MSP 的初始值,将其赋值给 MSP 就相当于复位 MSP。

(5)和(6)、使能中断。

(7)和(8)、数据同步和指令同步屏障。

(9),调用 SVC 指令触发 SVC 中断, SVC 也叫做请求管理调用, SVC 和 PendSV 异常对于OS 的设计来说非常重要。 SVC 异常由 SVC 指令触发。在 FreeRTOS 中仅仅使用 SVC 异常来启动第一个任务,后面的程序中就再也用不到 SVC 了

SVC 中断服务函数

在函数 prvStartFirstTask()中通过调用 SVC 指令触发了 SVC 中断,而第一个任务的启动就是在 SVC 中断服务函数中完成的, SVC 中断服务函数应该为 SVC_Handler(),但是FreeRTOSConfig.h 中通过#define 的方式重新定义为了 xPortPendSVHandler(),如下:

1
#define xPortPendSVHandler PendSV_Handler

空闲任务

函数 vTaskStartScheduler( )会创建一个名为“ IDLE”的任务,这个任务叫做空闲任务。顾名思义,空闲任务就是空闲的时候运行的任务,也就是系统中其他的任务由于各种原因不能运行的时候空闲任务就在运行。空闲任务是 FreeRTOS 系统自动创建的,不需要用户手动创建。任务调度器启动以后就必须有一个任务运行!但是空闲任务不仅仅是为了满足任务调度器启动以后至少有一个任务运行而创建的,空闲任务中还会去做一些其他的事情,如下:

1、判断系统是否有任务删除,如果有的话就在空闲任务中释放被删除任务的任务堆栈和任
务控制块的内存。
2、运行用户设置的空闲任务钩子函数。
3、判断是否开启低功耗 tickless 模式,如果开启的话还需要做相应的处理空闲任务的任务优先级是最低的,为 0,任务函数为 prvIdleTask()。

任务创建过程分析

任务创建函数分析

任务初始化函数分析

任务堆栈初始化函数分析

添加任务到就绪列表

任务删除过程分析

任务挂起过程分析

任务恢复过程分析